use Wx 0.15 qw[:allclasses];
use strict;
package wxLineGraph;

use Wx qw[:everything];
use base qw(Wx::Panel);
use strict;
use wxLineGraph::wxColourTextList;
use POSIX qw(ceil floor);


sub new {
	my( $self, $parent, $id, $pos, $size, $style, $name ) = @_;
	    
	$parent = undef              unless defined $parent;
	$id     = -1                 unless defined $id;
	$pos    = wxDefaultPosition  unless defined $pos;
	$size   = wxDefaultSize      unless defined $size;
	$name   = ""                 unless defined $name;

	$style = wxDEFAULT_FRAME_STYLE 
		unless defined $style;

    #If the panel is resized a full redraw is necessary
    $style = $style | wxFULL_REPAINT_ON_RESIZE;

	#Create controls
	$self = $self->SUPER::new( $parent, $id, $pos, $size, $style, $name );
	$self->{graph} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize);
#	$self->{legend} = wxColourTextList->new($self, -1);
	
	#Do the layout
	$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);

	$self->{sizer}->Add($self->{graph}, 1, wxEXPAND);
#	$self->{sizer}->Add($self->{legend}, 0, wxEXPAND);
	$self->SetSizer($self->{sizer});
	$self->{sizer}->Fit($self);
	$self->Layout();

	#Colours to cycle through
	$self->{colours} = [wxRED, wxBLUE, wxGREEN, wxCYAN];
	$self->{colour_index} = 0;

    #Register the painting event
    Wx::Event::EVT_PAINT($self,  \&OnPaint);
    Wx::Event::EVT_SIZE($self,  \&OnPaint);

	$self->OnPaint();
	return $self;
}

sub delay_plotting {
	my $self = shift;
	$self->{delay_plotting} = 1;
}

sub resume_plotting {
	my $self = shift;
	$self->{delay_plotting} = 0;
	$self->OnPaint();
}

#Clear the graph, ready for new input
sub clear {
	my $self = shift;
	delete $self->{lines};
	$self->{colour_index} = 0;
#	$self->{legend}->clear();
	$self->OnPaint();
}

#input: label, pointer to x data, pointer to y data, pointer to hash with plot options
sub add_line {
	my $self = shift;
	
	my $line = {
		'x' => shift,
		'y' => shift,
		'options' => $self->_default_options(shift),
		'label' => shift,
	};
	
	#Add the lines to lines needing plotting 
	push(@{$self->{lines}}, $line);
	
	#Add the string to the legend
#	$self->{legend}->add_string($line->{label}, $line->{options}->{colour},$line->{options}->{font});
	 
	#Redraw the graph
	$self->OnPaint();
}

sub set_x_label {
	my $self = shift;
	$self->set_label(@_, 'x');
}

sub set_y_label {
	my $self = shift;
	$self->set_label(@_, 'y');
}

#Set labels
sub set_label {
	my ($self, $label, $axis ) = @_;
	if (defined $label) {
		$self->{$axis.'_label'} = $label;
	} else {
		delete $self->{$axis.'_label'};
	}
	$self->OnPaint();
}

#Sets any unset options to their defaults
sub _default_options {
	my $self = shift;
	my $options = shift;
	unless (defined $options->{colour}) {
		$options->{colour} = $self->{colours}->[$self->{colour_index}];
		$self->{colour_index}++;
		if ($self->{colour_index} >= @{$self->{colours}}) {
			$self->{colour_index} = 0;
		}
		
	}
	unless (defined $options->{font}) { $options->{font} = wxNORMAL_FONT; }
	unless (defined $options->{width}) { $options->{width} = 2; }
	unless (defined $options->{style}) { $options->{style} = wxSOLID; }
	return $options;
}

#Draw the graph from scratch
sub OnPaint {
    my ($self, $event) = @_;
    if ($self->{delay_plotting}) { 
	    if (defined $event) { $event->Skip(); }
    	return; 
    }
 
    my $drawArea;

    #Depending whether the event is being called due to a change in the tree and 
    #not a redraw event we use different DC objects. Unsure how important this 
    #really is
    if (defined $event)
    {
        $drawArea = Wx::PaintDC->new($self->{graph});
    } else {
        $drawArea = Wx::ClientDC->new($self->{graph});
    }

    #Get the default background colour
    my $backColour = Wx::SystemSettings::GetColour(wxSYS_COLOUR_BACKGROUND);
    #Create a brush with that colour
    my $brush = Wx::Brush->new($backColour, wxSOLID);
    #Set the brush as the default background brush
    $drawArea->SetBackground($brush);
    #Clear the panel
    $drawArea->Clear;

    #Check we have something to plot
    unless (defined $self->{lines}) { 
	    if (defined $event) { $event->Skip(); }
    	return; 
    }
    
    #Get all x and y values so we can determine range
    my (@xval,@yval);
    foreach (@{$self->{lines}}) {
    	@xval = (@xval, @{$_->{x}}); 
    	@yval = (@yval, @{$_->{y}}); 
    }
    #Check there are some x and y values to plot! 
    unless (@xval > 1 && @yval > 1) { return; }
    @xval = sort {$a <=> $b} @xval;
    @yval = sort {$a <=> $b} @yval;
    
    
    $self->_set_scale($xval[0],$xval[-1],'x');
    $self->_set_scale($yval[0],$yval[-1],'y');    
    
    #Set the geometry, if the window is too small we are done
    unless ($self->_set_geometry($drawArea) ) { 
	    if (defined $event) { $event->Skip(); }
    	return; 
    }
    
    #Draw the axes
    $self->_draw_axes($drawArea);
    
    #Plot the lines
    $self->_plot_lines($drawArea);
    if (defined $event) { $event->Skip(); }
    
}

#Plot the lines
sub _plot_lines {
	my ($self, $dc) = @_;
	my $old_pen = $dc->GetPen();
	foreach (@{$self->{lines}}) {
    	my @xval = @{$_->{x}}; 
    	my @yval = @{$_->{y}};
    	my $pen = Wx::Pen->new($_->{options}->{colour},$_->{options}->{width},$_->{options}->{style});
    	$dc->SetPen($pen);
    	for (my $index = 0; $index < @xval-1 && $index < @yval-1; $index++) {
    		$dc->DrawLine($self->_map_value_pixel($xval[$index],'x'), 
    					  $self->_map_value_pixel($yval[$index],'y'),
				    	  $self->_map_value_pixel($xval[$index+1],'x'), 
    					  $self->_map_value_pixel($yval[$index+1],'y'));
    	} 
	}
	$dc->SetPen($old_pen);
}

#Sets border/x/y label areas etc. Returns 1 on success, 0 if the window is too small and nothing 
#should be drawn!
sub _set_geometry {
	my $self = shift;
	my $dc = shift;
	
	
	#Eventually the following variables should be dynamically determined in some clever way!
	
	#Gap between text
	my $text_gap = 2;
	#Height reserved for x tick labels
	my $x_ticklabelheight = 20;
	#Width reserved for y tick labels
	my $y_ticklabelwidth = 20;
	
	my $xlabel_h = 0;
	my $ylabel_w = 0;

	#Get the height of the label texts
	if (defined $self->{x_label}) {
    	my @temp = $dc->GetTextExtent($self->{x_label},$dc->GetFont);
    	$xlabel_h = $temp[1]; 
	} 
	if (defined $self->{y_label}) {
    	my @temp = $dc->GetTextExtent($self->{y_label},$dc->GetFont);
    	$ylabel_w = $temp[1]; 
	} 
	
	my ($win_w, $win_h) = $self->{graph}->GetSizeWH();
	
	#Check if the window is large enough
	if ($win_w< $ylabel_w+$text_gap*2+$y_ticklabelwidth || 
	    $win_h< $xlabel_h+$text_gap*2+$x_ticklabelheight) { return 0;}
	
	$self->{geom} = {
		'graph_x_min' => $ylabel_w+$text_gap*2+$y_ticklabelwidth,
		'graph_x_max' => $win_w-5,		
		'graph_y_max' => 0,
		'graph_y_min' => $win_h-($xlabel_h+$text_gap*2+$y_ticklabelwidth),		
	};
	$self->{geom}->{graph_x_span} = $self->{geom}->{graph_x_max} -$self->{geom}->{graph_x_min};
	$self->{geom}->{graph_y_span} = $self->{geom}->{graph_y_min} -$self->{geom}->{graph_y_max};

	return 1;
}

#Sets the scale for the axes, input is the minimum and maximum data value for an axis and 'x' or 'y'
sub _set_scale {
	my ($self, $min, $max, $axis) = @_;
	my $diff = $max-$min;
	
	if ($diff == 0) {
		$self->{$axis.'_scale'} = 1;
		$self->{$axis.'_min'} = $min-1;;
		$self->{$axis.'_max'} = $max+1;
		$self->{$axis.'_range'} = $self->{$axis.'_max'} - $self->{$axis.'_min'};
		return; 
	}
	my $multiplier = 10**floor(log($diff)/log(10));
	
	$self->{$axis.'_scale'} = $multiplier;
	$self->{$axis.'_min'} = floor($min/$multiplier)*$multiplier;
	$self->{$axis.'_max'} = ceil($max/$multiplier)*$multiplier;
	$self->{$axis.'_range'} = $self->{$axis.'_max'} - $self->{$axis.'_min'}; 
	
}

#Gets the tick mark locations for the axes, input is the axis name ('x' or 'y')
sub _get_tick_marks {
	my ($self, $axis) = @_;
	
	#Tick mark steps (in base units) for major and minor ticks
	my @steps = (1,0.5,0.2,0.1,0.05, 0.02, 0.01);

	#The minimum number of pixels for a major step
	my $step_pixel_min = 50;
	
	my $pixels = $self->{geom}->{'graph_'.$axis.'_span'};
	my $range = $self->{$axis.'_range'};
	my $scale = $self->{$axis.'_scale'};
	
	while (@steps) {
		my $step = pop(@steps);
		my $nsteps = ceil($range/($step*$scale));
		if ($pixels/$nsteps > $step_pixel_min) {
			my @output = 0 .. $nsteps;
			foreach (@output) { $_ = $_*$step*$self->{$axis.'_scale'}+$self->{$axis.'_min'}; }
			
			return @output
		}
	}
		
	#We default to two tick marks --> start and end		
	return ( $self->{$axis.'_min'}, $self->{$axis.'_max'} );
	
	
}

#Draw the axes
sub _draw_axes {
	my ($self, $dc) = @_;
	my $ticklength = 4;
	$dc->SetPen(wxBLACK_PEN);
	$dc->DrawRectangle($self->{geom}->{graph_x_min}, $self->{geom}->{graph_y_max},
					   $self->{geom}->{graph_x_span}, $self->{geom}->{graph_y_span});

	my @xticks = $self->_get_tick_marks('x');
	my @yticks = $self->_get_tick_marks('y');
	use Data::Dumper;
	
	foreach (@xticks) {
		my $x = $self->_map_value_pixel($_, 'x');
		if ($_==$xticks[-1]) {$x--;}
		$dc->DrawLine($x, $self->{geom}->{graph_y_min}-$ticklength, $x, $self->{geom}->{graph_y_min}+$ticklength);
		my ($t_w, $t_h, $r1, $r2) = $dc->GetTextExtent($_);
		
		if ($_==$xticks[-1]) {
			$dc->DrawText($_, floor($x-$t_w), floor( $self->{geom}->{graph_y_min} +$ticklength));
		} else {
			$dc->DrawText($_, floor($x-$t_w/2), floor( $self->{geom}->{graph_y_min} +$ticklength));
			
		}
	}
	foreach (@yticks) {
		my $y = $self->_map_value_pixel($_, 'y');
		if ($_==$yticks[0]) {$y--;}
		$dc->DrawLine($self->{geom}->{graph_x_min}-$ticklength, $y, $self->{geom}->{graph_x_min}+$ticklength, $y);
		my ($t_w, $t_h, $r1, $r2) = $dc->GetTextExtent($_);
		
		if ($_==$yticks[-1]) {
			$dc->DrawRotatedText($_, floor($self->{geom}->{graph_x_min}-$t_h-$ticklength), floor($y+$t_w), 90);
		} else {
			$dc->DrawRotatedText($_, floor($self->{geom}->{graph_x_min}-$t_h-$ticklength), floor($y+$t_w/2), 90);
		}
		
	}

	if (defined $self->{x_label}) {
		my ($t_w, $t_h, $r1, $r2) = $dc->GetTextExtent($self->{x_label});
		$dc->DrawText($self->{x_label}, $self->{geom}->{graph_x_min} +  $self->{geom}->{graph_x_span}/2-$t_w/2, $self->{geom}->{graph_y_min}+$ticklength+$t_h);
	} 
	
	if (defined $self->{y_label}) {
		my ($t_w, $t_h, $r1, $r2) = $dc->GetTextExtent($self->{x_label});
		$dc->DrawRotatedText($self->{y_label}, 0, $self->{geom}->{graph_y_max}+$self->{geom}->{graph_y_span}/2+$t_w/2,90);
	} 
	
						   
}

#Maps a value onto 0-1
sub _map_value {
	my ($self, $value, $axis) = @_;
	my $output;
	$output = ($value - $self->{$axis.'_min'})/$self->{$axis.'_range'};
	return $output;
}

#Maps a value to its pixel position
sub _map_value_pixel {
	my ($self, $value, $axis) = @_;
	my $output;
	$output = _map_value($self,$value,$axis);
	
	if ($axis eq 'x') {
		$output = floor($output * $self->{geom}->{graph_x_span} + $self->{geom}->{graph_x_min});
	} else {
		$output = floor($self->{geom}->{'graph_y_min'} - $output * $self->{geom}->{'graph_y_span'});
	}
	return $output;
}

1;